/* -------------------------------------------------------------------------
 *
 * pgstat_shmem.c
 *	  在共享内存中存储统计条目
 *
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/activity/pgstat_shmem.c
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "pgstat.h"
#include "storage/shmem.h"
#include "utils/memutils.h"
#include "utils/pgstat_internal.h"


#define PGSTAT_ENTRY_REF_HASH_SIZE	128

/* hash table entry for finding the PgStat_EntryRef for a key */
/* 哈希键 */
/* 用于 simplehash */
/* 用于引用共享统计条目 */
/* 共享哈希的参数 */
/* 从哈希条目链接新条目。 */
/* pgStatShmLookupCache的缓存年龄 */
/*
	 * 增加“世代”，以让任何具有本地引用的后端知道
	 * 它们指向的内容已过时。
	 */
/* 重新初始化内容 */
/*
 * pgstat_get_entry_ref() 的辅助函数。
 */
/*
 * pgstat_get_entry_ref() 的辅助函数。
 */
/*
	 * 我们立即插入一个缓存条目，因为这可以避免 1) 在缓存未命中情况下的多次
	 * 哈希表查找 2) 在增加 PgStatShared_Common->refcount 后处理
	 * 内存不足错误。
	 */
/* 应该至少有我们的引用 */
/* pgStatLocal.shmem本身的分配 */
/* 清除填充 */
/*
	 * 仅在我们可能创建条目的情况下传递 created_entry
	 * 是有意义的。
	 */
/*
	 * 检查其他后端是否丢弃了由于某人持有引用而无法删除的统计信息。
	 * 如果是这样，检查这个后端的引用。
	 * 这不应该经常发生。检查的位置有点随机，
	 * 但这是相对经常被调用的路径，因此比大多数情况要好。
	 */
/* 提升上述设置的限制 */
/*
	 * 首先在哈希表中进行查找 - 条目很可能已经存在，
	 * 这样我们只需要一个共享锁。
	 */
/* 初始化固定编号的统计信息 */
/* 统计共享内存在后端生命周期内持续存在 */
/* 我们不应该留下对共享统计的引用 */
/*
			 * 有合法的情况，旧的统计条目可能在重用之前还没有被删除。最明显的情况是复制槽统计，在删除后可以用相同的索引创建新的槽。但是oid回绕也可能导致其他情况。我们只是将统计数据重置为其普通状态，同时在任何剩余的本地引用的共享条目中增加它的“代”。
			 */
typedef struct PgStat_EntryRefHashEntry
{
	PgStat_HashKey key;			/* 哈希键 */
	char		status;			/* 用于 simplehash */
	PgStat_EntryRef *entry_ref;
} PgStat_EntryRefHashEntry;


/*
	 * 将引用计数初始化为1，标记为有效/未丢弃。该条目
	 * 在初始化之前不能被释放，因为在我们持有
	 * dshash 分区锁的情况下无法找到它。调用者如果需要
	 * 更长时间的引用，则需要进一步增加引用计数。
	 */
#define SH_PREFIX pgstat_entry_ref_hash
#define SH_ELEMENT_TYPE PgStat_EntryRefHashEntry
#define SH_KEY_TYPE PgStat_HashKey
#define SH_KEY key
#define SH_HASH_KEY(tb, key) \
	pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL)
#define SH_EQUAL(tb, a, b) \
	pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0
#define SH_SCOPE static inline
#define SH_DEFINE
#define SH_DECLARE
#include "lib/simplehash.h"


static void fc_pgstat_drop_database_and_contents(Oid fc_dboid);

static void fc_pgstat_free_entry(PgStatShared_HashEntry *fc_shent, dshash_seq_status *fc_hstat);

static void fc_pgstat_release_entry_ref(PgStat_HashKey fc_key, PgStat_EntryRef *fc_entry_ref, bool fc_discard_pending);
static bool fc_pgstat_need_entry_refs_gc(void);
static void fc_pgstat_gc_entry_refs(void);
static void fc_pgstat_release_all_entry_refs(bool fc_discard_pending);
typedef bool (*ReleaseMatchCB) (PgStat_EntryRefHashEntry *, Datum data);
static void fc_pgstat_release_matching_entry_refs(bool fc_discard_pending, ReleaseMatchCB fc_match, Datum fc_match_data);

static void fc_pgstat_setup_memcxt(void);


/*
	 * 将“世代”初始化为0，表示刚创建。
	 */
static const dshash_parameters dsh_params = {
	sizeof(PgStat_HashKey),
	sizeof(PgStatShared_HashEntry),
	pgstat_cmp_hash_key,
	pgstat_hash_hash_key,
	LWTRANCHE_PGSTATS_HASH
};


/* 从哈希条目链接新条目。 */
static pgstat_entry_ref_hash_hash *pgStatEntryRefHash = NULL;
static int	pgStatSharedRefAge = 0; /* 标记为不再丢弃 */

/*
	 * 增加“世代”，以让任何具有本地引用的后端知道
	 * 它们指向的内容已过时。
	 */
static MemoryContext pgStatSharedRefContext = NULL;
static MemoryContext pgStatEntryRefHashContext = NULL;


/* 重新初始化内容 */

/*
 * pgstat_get_entry_ref() 的辅助函数。
 */
static Size fc_pgstat_dsa_init_size(void)
{
	Size		fc_sz;

	/*
 * pgstat_get_entry_ref() 的辅助函数。
 */
	fc_sz = 256 * 1024;
	Assert(dsa_minimum_size() <= fc_sz);
	return MAXALIGN(fc_sz);
}

/*
	 * 我们立即插入一个缓存条目，因为这可以避免 1) 在缓存未命中情况下的多次
	 * 哈希表查找 2) 在增加 PgStatShared_Common->refcount 后处理
	 * 内存不足错误。
	 */
Size StatsShmemSize(void)
{
	Size		fc_sz;

	fc_sz = MAXALIGN(sizeof(PgStat_ShmemControl));
	fc_sz = add_size(fc_sz, fc_pgstat_dsa_init_size());

	return fc_sz;
}

/* 应该至少有我们的引用 */
void StatsShmemInit(void)
{
	bool		fc_found;
	Size		fc_sz;

	fc_sz = StatsShmemSize();
	pgStatLocal.shmem = (PgStat_ShmemControl *)
		ShmemInitStruct("Shared Memory Stats", fc_sz, &fc_found);

	if (!IsUnderPostmaster)
	{
		dsa_area   *fc_dsa;
		dshash_table *fc_dsh;
		PgStat_ShmemControl *fc_ctl = pgStatLocal.shmem;
		char	   *fc_p = (char *) fc_ctl;

		Assert(!fc_found);

		/*
 * 获取共享统计引用。如果 create 为真，当共享统计对象
 * 不存在时将创建它。
 *
 * 当 create 为真，并且 created_entry 非 NULL 时，如果条目是新创建的，
 * 将其设置为 true，否则为 false。
 */
		fc_p += MAXALIGN(sizeof(PgStat_ShmemControl));

		/* 清除填充 */
		fc_ctl->raw_dsa_area = fc_p;
		fc_p += MAXALIGN(fc_pgstat_dsa_init_size());
		fc_dsa = dsa_create_in_place(fc_ctl->raw_dsa_area,
								  fc_pgstat_dsa_init_size(),
								  LWTRANCHE_PGSTATS_DSA, 0);
		dsa_pin(fc_dsa);

		/*
	 * 仅在我们可能创建条目的情况下传递 created_entry
	 * 是有意义的。
	 */
		dsa_set_size_limit(fc_dsa, fc_pgstat_dsa_init_size());

		/*
	 * 检查其他后端是否丢弃了由于某人持有引用而无法删除的统计信息。
	 * 如果是这样，检查这个后端的引用。
	 * 这不应该经常发生。检查的位置有点随机，
	 * 但这是相对经常被调用的路径，因此比大多数情况要好。
	 */
		fc_dsh = dshash_create(fc_dsa, &dsh_params, 0);
		fc_ctl->hash_handle = dshash_get_hash_table_handle(fc_dsh);

		/*
	 * 首先检查本地内存中的查找缓存哈希表。如果我们在这里找到匹配，
	 * 我们可以避免获取锁/造成竞争。
	 */
		dsa_set_size_limit(fc_dsa, -1);

		/*
	 * 首先在哈希表中进行查找 - 条目很可能已经存在，
	 * 这样我们只需要一个共享锁。
	 */
		dshash_detach(fc_dsh);
		dsa_detach(fc_dsa);

		pg_atomic_init_u64(&fc_ctl->gc_request_count, 1);


		/*
		 * 可能在上述查找之后会有人创建条目。如果是这样，
		 * 则继续流向与如果在调用 dshash_find() 之前已创建条目时相同的路径。
		 */
		LWLockInitialize(&fc_ctl->archiver.lock, LWTRANCHE_PGSTATS_DATA);
		LWLockInitialize(&fc_ctl->bgwriter.lock, LWTRANCHE_PGSTATS_DATA);
		LWLockInitialize(&fc_ctl->checkpointer.lock, LWTRANCHE_PGSTATS_DATA);
		LWLockInitialize(&fc_ctl->slru.lock, LWTRANCHE_PGSTATS_DATA);
		LWLockInitialize(&fc_ctl->wal.lock, LWTRANCHE_PGSTATS_DATA);
	}
	else
	{
		Assert(fc_found);
	}
}

void pgstat_attach_shmem(void)
{
	MemoryContext fc_oldcontext;

	Assert(pgStatLocal.dsa == NULL);

	/*
		 * 如果我们不创建，则再次删除引用。很可能这只是一个统计查找 -
		 * 没有必要浪费内存来维护一个指向无效数据的共享引用...
		 */
	fc_oldcontext = MemoryContextSwitchTo(TopMemoryContext);

	pgStatLocal.dsa = dsa_attach_in_place(pgStatLocal.shmem->raw_dsa_area,
										  NULL);
	dsa_pin_mapping(pgStatLocal.dsa);

	pgStatLocal.shared_hash = dshash_attach(pgStatLocal.dsa, &dsh_params,
											pgStatLocal.shmem->hash_handle, 0);

	MemoryContextSwitchTo(fc_oldcontext);
}

void pgstat_detach_shmem(void)
{
	Assert(pgStatLocal.dsa);

	/*
		 * 到这里可能是因为 dshash_find() 找到了匹配项，或者
		 * 如果 dshash_find_or_insert() 找到了一个并发插入的条目。
		 */
	fc_pgstat_release_all_entry_refs(false);

	dshash_detach(pgStatLocal.shared_hash);
	pgStatLocal.shared_hash = NULL;

	dsa_detach(pgStatLocal.dsa);

	/*
			 * 有合法的情况，旧的统计条目可能在重用之前还没有被删除。最明显的情况是复制槽统计，在删除后可以用相同的索引创建新的槽。但是oid回绕也可能导致其他情况。我们只是将统计数据重置为其普通状态，同时在任何剩余的本地引用的共享条目中增加它的“代”。
			 */
	dsa_release_in_place(pgStatLocal.shmem->raw_dsa_area);

	pgStatLocal.dsa = NULL;
}


/*
		 * 这不能与另一个后端查找统计条目并增加引用计数竞争，因为创建对已删除条目的额外引用是“不合法”的。
		 */

PgStatShared_Common *
pgstat_init_entry(PgStat_Kind fc_kind,
				  PgStatShared_HashEntry *fc_shhashent)
{
	/*
			 * 我们是这个条目的最后一个引用者，尝试删除共享条目。
			 */
	dsa_pointer fc_chunk;
	PgStatShared_Common *fc_shheader;

	/* 只有已删除的条目才能达到0引用计数 */
	pg_atomic_init_u32(&fc_shhashent->refcount, 1);

	/*
			 * 在尝试释放条目时，该条目可能已经被重新初始化，因此请再次确认它在持有共享条目的锁时没有被重用。
			 */
	pg_atomic_init_u32(&fc_shhashent->generation, 0);
	fc_shhashent->dropped = false;

	fc_chunk = dsa_allocate0(pgStatLocal.dsa, pgstat_get_kind_info(fc_kind)->shared_size);
	fc_shheader = dsa_get_address(pgStatLocal.dsa, fc_chunk);
	fc_shheader->magic = 0xdeadbeef;

	/* 相同的“代”，因此我们可以安全删除 */
	fc_shhashent->body = fc_chunk;

	LWLockInitialize(&fc_shheader->lock, LWTRANCHE_PGSTATS_DATA);

	return fc_shheader;
}

static PgStatShared_Common *
fc_pgstat_reinit_entry(PgStat_Kind fc_kind, PgStatShared_HashEntry *fc_shhashent)
{
	PgStatShared_Common *fc_shheader;

	fc_shheader = dsa_get_address(pgStatLocal.dsa, fc_shhashent->body);

	/*
				 * 共享统计条目已被重新初始化，因此不要删除其共享条目，只需释放其锁。
				 */
	pg_atomic_fetch_add_u32(&fc_shhashent->refcount, 1);

	/*
 * 与pgstat_lock_entry()分开，因为大多数调用者需要独占锁定。
 */
	pg_atomic_fetch_add_u32(&fc_shhashent->generation, 1);
	fc_shhashent->dropped = false;

	/*
 * 获取和锁定共享统计的辅助函数。
 */
	Assert(fc_shheader->magic == 0xdeadbeef);
	memset(pgstat_get_entry_data(fc_kind, fc_shheader), 0,
		   pgstat_get_entry_len(fc_kind));

	return fc_shheader;
}

static void fc_pgstat_setup_shared_refs(void)
{
	if (likely(pgStatEntryRefHash != NULL))
		return;

	pgStatEntryRefHash =
		pgstat_entry_ref_hash_create(pgStatEntryRefHashContext,
									 PGSTAT_ENTRY_REF_HASH_SIZE, NULL);
	pgStatSharedRefAge = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
	Assert(pgStatSharedRefAge != 0);
}

/* 查找对应于本地条目的共享表统计条目 */
static void fc_pgstat_acquire_entry_ref(PgStat_EntryRef *fc_entry_ref,
						 PgStatShared_HashEntry *fc_shhashent,
						 PgStatShared_Common *fc_shheader)
{
	Assert(fc_shheader->magic == 0xdeadbeef);
	Assert(pg_atomic_read_u32(&fc_shhashent->refcount) > 0);

	pg_atomic_fetch_add_u32(&fc_shhashent->refcount, 1);

	dshash_release_lock(pgStatLocal.shared_hash, fc_shhashent);

	fc_entry_ref->shared_stats = fc_shheader;
	fc_entry_ref->shared_entry = fc_shhashent;
	fc_entry_ref->generation = pg_atomic_read_u32(&fc_shhashent->generation);
}

/* 查找对应于本地条目的共享表统计条目 */
static bool fc_pgstat_get_entry_ref_cached(PgStat_HashKey fc_key, PgStat_EntryRef **fc_entry_ref_p)
{
	bool		fc_found;
	PgStat_EntryRefHashEntry *fc_cache_entry;

	/* 在创建pgStatEntryRefHash时应该已经初始化 */

	fc_cache_entry = pgstat_entry_ref_hash_insert(pgStatEntryRefHash, fc_key, &fc_found);

	if (!fc_found || !fc_cache_entry->entry_ref)
	{
		PgStat_EntryRef *fc_entry_ref;

		fc_cache_entry->entry_ref = fc_entry_ref =
			MemoryContextAlloc(pgStatSharedRefContext,
							   sizeof(PgStat_EntryRef));
		fc_entry_ref->shared_stats = NULL;
		fc_entry_ref->shared_entry = NULL;
		fc_entry_ref->pending = NULL;

		fc_found = false;
	}
	else if (fc_cache_entry->entry_ref->shared_stats == NULL)
	{
		Assert(fc_cache_entry->entry_ref->pending == NULL);
		fc_found = false;
	}
	else
	{
		PgStat_EntryRef *fc_entry_ref PG_USED_FOR_ASSERTS_ONLY;

		fc_entry_ref = fc_cache_entry->entry_ref;
		Assert(fc_entry_ref->shared_entry != NULL);
		Assert(fc_entry_ref->shared_stats != NULL);

		Assert(fc_entry_ref->shared_stats->magic == 0xdeadbeef);
		/*
	 * 一些条目已经被删除或重新初始化。使指向它们的缓存无效。
	 */
		Assert(pg_atomic_read_u32(&fc_entry_ref->shared_entry->refcount) > 0);
	}

	*fc_entry_ref_p = fc_cache_entry->entry_ref;
	return fc_found;
}

/*
		 * “代”检查条目被重新初始化的情况，“删除”则是这些条目被删除的情况。
		 */
PgStat_EntryRef * pgstat_get_entry_ref(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid, bool fc_create,
					 bool *fc_created_entry)
{
	PgStat_HashKey fc_key;
	PgStatShared_HashEntry *fc_shhashent;
	PgStatShared_Common *fc_shheader = NULL;
	PgStat_EntryRef *fc_entry_ref;

	/* 清除填充 */
	memset(&fc_key, 0, sizeof(struct PgStat_HashKey));

	fc_key.kind = fc_kind;
	fc_key.dboid = fc_dboid;
	fc_key.objoid = fc_objoid;

	/*
 * 释放对共享统计条目的所有本地引用。
 *
 * 当进程退出时，它不能在仍然持有对统计条目的引用的情况下退出，否则共享统计条目将永远无法被释放。
 */
	AssertArg(fc_create || fc_created_entry == NULL);
	pgstat_assert_is_up();
	Assert(pgStatLocal.shared_hash != NULL);
	Assert(!pgStatLocal.shmem->is_shutdown);

	fc_pgstat_setup_memcxt();
	fc_pgstat_setup_shared_refs();

	if (fc_created_entry != NULL)
		*fc_created_entry = false;

	/* 丢弃待处理 = */
	if (fc_pgstat_need_entry_refs_gc())
		fc_pgstat_gc_entry_refs();

	/* ------------------------------------------------------------
 * 删除和重置统计条目
 * ------------------------------------------------------------
 */
	if (fc_pgstat_get_entry_ref_cached(fc_key, &fc_entry_ref))
		return fc_entry_ref;

	Assert(fc_entry_ref != NULL);

	/*
	 * 在删除条目之前获取dsa指针 - 这样我们可以在释放锁后释放内存。
	 */
	fc_shhashent = dshash_find(pgStatLocal.shared_hash, &fc_key, false);

	if (fc_create && !fc_shhashent)
	{
		bool		fc_shfound;

		/*
 * pgstat_drop_database_and_contents()和pgstat_drop_entry()的辅助函数。如果hstat非空，使用dshash_delete_current()删除共享条目，否则使用dshash_delete_entry()。在这两种情况下，条目都需要已经被锁定。
 */
		fc_shhashent = dshash_find_or_insert(pgStatLocal.shared_hash, &fc_key, &fc_shfound);
		if (!fc_shfound)
		{
			fc_shheader = pgstat_init_entry(fc_kind, fc_shhashent);
			fc_pgstat_acquire_entry_ref(fc_entry_ref, fc_shhashent, fc_shheader);

			if (fc_created_entry != NULL)
				*fc_created_entry = true;

			return fc_entry_ref;
		}
	}

	if (!fc_shhashent)
	{
		/* 应该已经释放本地引用 */
		fc_pgstat_release_entry_ref(fc_key, fc_entry_ref, false);

		return NULL;
	}
	else
	{
		/*
	 * 发信号表明条目已被删除 - 这最终会导致其他后端释放它们的引用。
	 */

		if (fc_shhashent->dropped && fc_create)
		{
			/* 释放引用计数，标记条目为未删除 */
			fc_shheader = fc_pgstat_reinit_entry(fc_kind, fc_shhashent);
			fc_pgstat_acquire_entry_ref(fc_entry_ref, fc_shhashent, fc_shheader);

			if (fc_created_entry != NULL)
				*fc_created_entry = true;

			return fc_entry_ref;
		}
		else if (fc_shhashent->dropped)
		{
			dshash_release_lock(pgStatLocal.shared_hash, fc_shhashent);
			fc_pgstat_release_entry_ref(fc_key, fc_entry_ref, false);

			return NULL;
		}
		else
		{
			fc_shheader = dsa_get_address(pgStatLocal.dsa, fc_shhashent->body);
			fc_pgstat_acquire_entry_ref(fc_entry_ref, fc_shhashent, fc_shheader);

			return fc_entry_ref;
		}
	}
}

static void fc_pgstat_release_entry_ref(PgStat_HashKey fc_key, PgStat_EntryRef *fc_entry_ref,
						 bool fc_discard_pending)
{
	if (fc_entry_ref && fc_entry_ref->pending)
	{
		if (fc_discard_pending)
			pgstat_delete_pending_entry(fc_entry_ref);
		else
			elog(ERROR, "releasing ref with pending data");
	}

	if (fc_entry_ref && fc_entry_ref->shared_stats)
	{
		Assert(fc_entry_ref->shared_stats->magic == 0xdeadbeef);
		Assert(fc_entry_ref->pending == NULL);

		/*
 * 删除数据库及其内部所有对象的统计信息。
 */
		if (pg_atomic_fetch_sub_u32(&fc_entry_ref->shared_entry->refcount, 1) == 1)
		{
			PgStatShared_HashEntry *fc_shent;

			/*
	 * 这个后端可能是唯一一个持有即将被删除条目的引用的后端。
	 * 确保我们不会阻止它在稍后被清理。
	 *
	 * 与下面的 dshash 迭代分开执行这一操作，可以避免在持有共享哈希表的分区锁时进行。
	 */

			/* 一些 dshash 条目将被移除，获取独占锁。 */
			Assert(fc_entry_ref->shared_entry->dropped);

			fc_shent = dshash_find(pgStatLocal.shared_hash,
								&fc_entry_ref->shared_entry->key,
								true);
			if (!fc_shent)
				elog(ERROR, "could not find just referenced shared stats entry");

			/*
			 * 被删除数据库的统计信息可能当前仍在被访问
			 * （例如考虑 pg_stat_database 的数据库统计信息）。
			 */
			if (pg_atomic_read_u32(&fc_entry_ref->shared_entry->generation) ==
				fc_entry_ref->generation)
			{
				/*
	 * 如果无法释放某些统计数据，通知引用持有者运行其缓存的 pgStatShmLookupCache 的垃圾收集。
	 */
				Assert(pg_atomic_read_u32(&fc_entry_ref->shared_entry->refcount) == 0);
				Assert(fc_entry_ref->shared_entry == fc_shent);
				fc_pgstat_free_entry(fc_shent, NULL);
			}
			else
			{
				/*
 * 删除单个统计条目。
 *
 * 如果被删除对象的统计条目无法释放，则此例程返回 false，反之返回 true。
 *
 * 如果无法释放统计条目，则调用此函数的调用者应调用 pgstat_request_entry_refs_gc()
 * 以确保该条目的内存可以在稍后由其他后端调用
 * pgstat_gc_entry_refs() 来回收。
 */
				dshash_release_lock(pgStatLocal.shared_hash, fc_shent);
			}
		}
	}

	if (!pgstat_entry_ref_hash_delete(pgStatEntryRefHash, fc_key))
		elog(ERROR, "entry ref vanished before deletion");

	if (fc_entry_ref)
		pfree(fc_entry_ref);
}

bool pgstat_lock_entry(PgStat_EntryRef *fc_entry_ref, bool fc_nowait)
{
	LWLock	   *fc_lock = &fc_entry_ref->shared_stats->lock;

	if (fc_nowait)
		return LWLockConditionalAcquire(fc_lock, LW_EXCLUSIVE);

	LWLockAcquire(fc_lock, LW_EXCLUSIVE);
	return true;
}

/* 清除填充 */
bool pgstat_lock_entry_shared(PgStat_EntryRef *fc_entry_ref, bool fc_nowait)
{
	LWLock	   *fc_lock = &fc_entry_ref->shared_stats->lock;

	if (fc_nowait)
		return LWLockConditionalAcquire(fc_lock, LW_SHARED);

	LWLockAcquire(fc_lock, LW_SHARED);
	return true;
}

void pgstat_unlock_entry(PgStat_EntryRef *fc_entry_ref)
{
	LWLockRelease(&fc_entry_ref->shared_stats->lock);
}

/* 删除本地引用 */
PgStat_EntryRef * pgstat_get_entry_ref_locked(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid,
							bool fc_nowait)
{
	PgStat_EntryRef *fc_entry_ref;

	/* 在共享哈希表中将条目标记为已删除，如果可能则删除 */
	fc_entry_ref = pgstat_get_entry_ref(fc_kind, fc_dboid, fc_objoid, true, NULL);

	/*
		 * 数据库统计信息包含其他统计信息。在删除数据库时也删除这些信息。XXX：也许这应该以稍微更原则的方式进行？
		 * 但目前还不清楚那会是什么样，迄今为止这是唯一的情况...
		 */
	if (!pgstat_lock_entry(fc_entry_ref, fc_nowait))
		return NULL;

	return fc_entry_ref;
}

void pgstat_request_entry_refs_gc(void)
{
	pg_atomic_fetch_add_u64(&pgStatLocal.shmem->gc_request_count, 1);
}

static bool fc_pgstat_need_entry_refs_gc(void)
{
	uint64		fc_curage;

	if (!pgStatEntryRefHash)
		return false;

	/*
 * 重置一个可变编号的统计条目。
 */
	Assert(pgStatSharedRefAge != 0);

	fc_curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);

	return pgStatSharedRefAge != fc_curage;
}

static void fc_pgstat_gc_entry_refs(void)
{
	pgstat_entry_ref_hash_iterator fc_i;
	PgStat_EntryRefHashEntry *fc_ent;
	uint64		fc_curage;

	fc_curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
	Assert(fc_curage != 0);

	/*
 * 扫描统计信息的共享哈希表，如果提供的 do_reset() 函数批准，则重置统计信息。
 */
	pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &fc_i);
	while ((fc_ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &fc_i)) != NULL)
	{
		PgStat_EntryRef *fc_entry_ref = fc_ent->entry_ref;

		Assert(!fc_entry_ref->shared_stats ||
			   fc_entry_ref->shared_stats->magic == 0xdeadbeef);

		/* dshash 条目未被修改，获取共享锁 */
		if (!fc_entry_ref->shared_entry->dropped &&
			pg_atomic_read_u32(&fc_entry_ref->shared_entry->generation) ==
			fc_entry_ref->generation)
			continue;

		/* cannot gc shared ref that has pending data */
		if (fc_entry_ref->pending != NULL)
			continue;

		fc_pgstat_release_entry_ref(fc_ent->key, fc_entry_ref, false);
	}

	pgStatSharedRefAge = fc_curage;
}

static void fc_pgstat_release_matching_entry_refs(bool fc_discard_pending, ReleaseMatchCB fc_match,
								   Datum fc_match_data)
{
	pgstat_entry_ref_hash_iterator fc_i;
	PgStat_EntryRefHashEntry *fc_ent;

	if (pgStatEntryRefHash == NULL)
		return;

	pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &fc_i);

	while ((fc_ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &fc_i))
		   != NULL)
	{
		Assert(fc_ent->entry_ref != NULL);

		if (fc_match && !fc_match(fc_ent, fc_match_data))
			continue;

		fc_pgstat_release_entry_ref(fc_ent->key, fc_ent->entry_ref, fc_discard_pending);
	}
}

/*
 * Release all local references to shared stats entries.
 *
 * When a process exits it cannot do so while still holding references onto
 * stats entries, otherwise the shared stats entries could never be freed.
 */
static void fc_pgstat_release_all_entry_refs(bool fc_discard_pending)
{
	if (pgStatEntryRefHash == NULL)
		return;

	fc_pgstat_release_matching_entry_refs(fc_discard_pending, NULL, 0);
	Assert(pgStatEntryRefHash->members == 0);
	pgstat_entry_ref_hash_destroy(pgStatEntryRefHash);
	pgStatEntryRefHash = NULL;
}

static bool fc_match_db(PgStat_EntryRefHashEntry *fc_ent, Datum fc_match_data)
{
	Oid			fc_dboid = DatumGetObjectId(fc_match_data);

	return fc_ent->key.dboid == fc_dboid;
}

static void fc_pgstat_release_db_entry_refs(Oid fc_dboid)
{
	fc_pgstat_release_matching_entry_refs( /* discard pending = */ true,
									   fc_match_db,
									   ObjectIdGetDatum(fc_dboid));
}


/* ------------------------------------------------------------
 * Dropping and resetting of stats entries
 * ------------------------------------------------------------
 */

static void fc_pgstat_free_entry(PgStatShared_HashEntry *fc_shent, dshash_seq_status *fc_hstat)
{
	dsa_pointer fc_pdsa;

	/*
	 * Fetch dsa pointer before deleting entry - that way we can free the
	 * memory after releasing the lock.
	 */
	fc_pdsa = fc_shent->body;

	if (!fc_hstat)
		dshash_delete_entry(pgStatLocal.shared_hash, fc_shent);
	else
		dshash_delete_current(fc_hstat);

	dsa_free(pgStatLocal.dsa, fc_pdsa);
}

/*
 * Helper for both pgstat_drop_database_and_contents() and
 * pgstat_drop_entry(). If hstat is non-null delete the shared entry using
 * dshash_delete_current(), otherwise use dshash_delete_entry(). In either
 * case the entry needs to be already locked.
 */
static bool fc_pgstat_drop_entry_internal(PgStatShared_HashEntry *fc_shent,
						   dshash_seq_status *fc_hstat)
{
	Assert(fc_shent->body != InvalidDsaPointer);

	/* should already have released local reference */
	if (pgStatEntryRefHash)
		Assert(!pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, fc_shent->key));

	/*
	 * Signal that the entry is dropped - this will eventually cause other
	 * backends to release their references.
	 */
	if (fc_shent->dropped)
		elog(ERROR,
			 "trying to drop stats entry already dropped: kind=%s dboid=%u objoid=%u refcount=%u",
			 pgstat_get_kind_info(fc_shent->key.kind)->name,
			 fc_shent->key.dboid, fc_shent->key.objoid,
			 pg_atomic_read_u32(&fc_shent->refcount));
	fc_shent->dropped = true;

	/* release refcount marking entry as not dropped */
	if (pg_atomic_sub_fetch_u32(&fc_shent->refcount, 1) == 0)
	{
		fc_pgstat_free_entry(fc_shent, fc_hstat);
		return true;
	}
	else
	{
		if (!fc_hstat)
			dshash_release_lock(pgStatLocal.shared_hash, fc_shent);
		return false;
	}
}

/*
 * Drop stats for the database and all the objects inside that database.
 */
static void fc_pgstat_drop_database_and_contents(Oid fc_dboid)
{
	dshash_seq_status fc_hstat;
	PgStatShared_HashEntry *fc_p;
	uint64		fc_not_freed_count = 0;

	Assert(OidIsValid(fc_dboid));

	Assert(pgStatLocal.shared_hash != NULL);

	/*
	 * This backend might very well be the only backend holding a reference to
	 * about-to-be-dropped entries. Ensure that we're not preventing it from
	 * being cleaned up till later.
	 *
	 * Doing this separately from the dshash iteration below avoids having to
	 * do so while holding a partition lock on the shared hashtable.
	 */
	fc_pgstat_release_db_entry_refs(fc_dboid);

	/* some of the dshash entries are to be removed, take exclusive lock. */
	dshash_seq_init(&fc_hstat, pgStatLocal.shared_hash, true);
	while ((fc_p = dshash_seq_next(&fc_hstat)) != NULL)
	{
		if (fc_p->dropped)
			continue;

		if (fc_p->key.dboid != fc_dboid)
			continue;

		if (!fc_pgstat_drop_entry_internal(fc_p, &fc_hstat))
		{
			/*
			 * Even statistics for a dropped database might currently be
			 * accessed (consider e.g. database stats for pg_stat_database).
			 */
			fc_not_freed_count++;
		}
	}
	dshash_seq_term(&fc_hstat);

	/*
	 * If some of the stats data could not be freed, signal the reference
	 * holders to run garbage collection of their cached pgStatShmLookupCache.
	 */
	if (fc_not_freed_count > 0)
		pgstat_request_entry_refs_gc();
}

/*
 * Drop a single stats entry.
 *
 * This routine returns false if the stats entry of the dropped object could
 * not be freed, true otherwise.
 *
 * The callers of this function should call pgstat_request_entry_refs_gc()
 * if the stats entry could not be freed, to ensure that this entry's memory
 * can be reclaimed later by a different backend calling
 * pgstat_gc_entry_refs().
 */
bool pgstat_drop_entry(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid)
{
	PgStat_HashKey fc_key;
	PgStatShared_HashEntry *fc_shent;
	bool		fc_freed = true;

	/* 清除填充 */
	memset(&fc_key, 0, sizeof(struct PgStat_HashKey));

	fc_key.kind = fc_kind;
	fc_key.dboid = fc_dboid;
	fc_key.objoid = fc_objoid;

	/* delete local reference */
	if (pgStatEntryRefHash)
	{
		PgStat_EntryRefHashEntry *fc_lohashent =
		pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, fc_key);

		if (fc_lohashent)
			fc_pgstat_release_entry_ref(fc_lohashent->key, fc_lohashent->entry_ref,
									 true);
	}

	/* mark entry in shared hashtable as deleted, drop if possible */
	fc_shent = dshash_find(pgStatLocal.shared_hash, &fc_key, true);
	if (fc_shent)
	{
		fc_freed = fc_pgstat_drop_entry_internal(fc_shent, NULL);

		/*
		 * Database stats contain other stats. Drop those as well when
		 * dropping the database. XXX: Perhaps this should be done in a
		 * slightly more principled way? But not obvious what that'd look
		 * like, and so far this is the only case...
		 */
		if (fc_key.kind == PGSTAT_KIND_DATABASE)
			fc_pgstat_drop_database_and_contents(fc_key.dboid);
	}

	return fc_freed;
}

void pgstat_drop_all_entries(void)
{
	dshash_seq_status fc_hstat;
	PgStatShared_HashEntry *fc_ps;
	uint64		fc_not_freed_count = 0;

	dshash_seq_init(&fc_hstat, pgStatLocal.shared_hash, true);
	while ((fc_ps = dshash_seq_next(&fc_hstat)) != NULL)
	{
		if (fc_ps->dropped)
			continue;

		if (!fc_pgstat_drop_entry_internal(fc_ps, &fc_hstat))
			fc_not_freed_count++;
	}
	dshash_seq_term(&fc_hstat);

	if (fc_not_freed_count > 0)
		pgstat_request_entry_refs_gc();
}

static void fc_shared_stat_reset_contents(PgStat_Kind fc_kind, PgStatShared_Common *fc_header,
						   TimestampTz fc_ts)
{
	const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);

	memset(pgstat_get_entry_data(fc_kind, fc_header), 0,
		   pgstat_get_entry_len(fc_kind));

	if (fc_kind_info->reset_timestamp_cb)
		fc_kind_info->reset_timestamp_cb(fc_header, fc_ts);
}

/*
 * Reset one variable-numbered stats entry.
 */
void pgstat_reset_entry(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid, TimestampTz fc_ts)
{
	PgStat_EntryRef *fc_entry_ref;

	Assert(!pgstat_get_kind_info(fc_kind)->fixed_amount);

	fc_entry_ref = pgstat_get_entry_ref(fc_kind, fc_dboid, fc_objoid, false, NULL);
	if (!fc_entry_ref || fc_entry_ref->shared_entry->dropped)
		return;

	(void) pgstat_lock_entry(fc_entry_ref, false);
	fc_shared_stat_reset_contents(fc_kind, fc_entry_ref->shared_stats, fc_ts);
	pgstat_unlock_entry(fc_entry_ref);
}

/*
 * Scan through the shared hashtable of stats, resetting statistics if
 * approved by the provided do_reset() function.
 */
void pgstat_reset_matching_entries(bool (*fc_do_reset) (PgStatShared_HashEntry *, Datum),
							  Datum fc_match_data, TimestampTz fc_ts)
{
	dshash_seq_status fc_hstat;
	PgStatShared_HashEntry *fc_p;

	/* dshash entry is not modified, take shared lock */
	dshash_seq_init(&fc_hstat, pgStatLocal.shared_hash, false);
	while ((fc_p = dshash_seq_next(&fc_hstat)) != NULL)
	{
		PgStatShared_Common *fc_header;

		if (fc_p->dropped)
			continue;

		if (!fc_do_reset(fc_p, fc_match_data))
			continue;

		fc_header = dsa_get_address(pgStatLocal.dsa, fc_p->body);

		LWLockAcquire(&fc_header->lock, LW_EXCLUSIVE);

		fc_shared_stat_reset_contents(fc_p->key.kind, fc_header, fc_ts);

		LWLockRelease(&fc_header->lock);
	}
	dshash_seq_term(&fc_hstat);
}

static bool fc_match_kind(PgStatShared_HashEntry *fc_p, Datum fc_match_data)
{
	return fc_p->key.kind == DatumGetInt32(fc_match_data);
}

void pgstat_reset_entries_of_kind(PgStat_Kind fc_kind, TimestampTz fc_ts)
{
	pgstat_reset_matching_entries(fc_match_kind, Int32GetDatum(fc_kind), fc_ts);
}

static void fc_pgstat_setup_memcxt(void)
{
	if (unlikely(!pgStatSharedRefContext))
		pgStatSharedRefContext =
			AllocSetContextCreate(TopMemoryContext,
								  "PgStat Shared Ref",
								  ALLOCSET_SMALL_SIZES);
	if (unlikely(!pgStatEntryRefHashContext))
		pgStatEntryRefHashContext =
			AllocSetContextCreate(TopMemoryContext,
								  "PgStat Shared Ref Hash",
								  ALLOCSET_SMALL_SIZES);
}
